package upnp

import (
	"encoding/xml"
	"fmt"
	"net/http"
	"strings"
)

// GetPortMappingAction get port mapping info by index
type GetPortMappingAction struct {
	actionData
}

func newGetPortMappingAction(srv *Service, in, out []string) *GetPortMappingAction {
	return &GetPortMappingAction{
		actionData{
			name:  "GetGenericPortMappingEntry",
			srv:   srv,
			args:  in,
			resps: out,
		},
	}
}

type getPortMappingBody struct {
	ActionBodyContent
	Index int `xml:"NewPortMappingIndex"`
}

func (a *GetPortMappingAction) requestBody(i int) (*actionBodyType, error) {
	body := new(getPortMappingBody)
	body.Init(a.actionData)
	body.Index = i
	return newActionBody(body)
}

type getPortMappingResponse struct {
	Body struct {
		Host        string `xml:"NewRemoteHost"`
		ExtPort     int    `xml:"NewExternalPort"`
		Protocol    string `xml:"NewProtocol"`
		LocalIP     string `xml:"NewInternalClient"`
		LocalPort   int    `xml:"NewInternalPort"`
		Enabled     int    `xml:"NewEnabled"`
		Description string `xml:"NewPortMappingDescription"`
		Duration    int    `xml:"NewLeaseDuration"`
	} `xml:"Body>GetGenericPortMappingEntryResponse"`
	Code   string `xml:"Body>Fault>faultcode"`
	String string `xml:"Body>Fault>faultstring"`
	Detail struct {
		UPnPError struct {
			Code        int    `xml:"errorCode"`
			Description string `xml:"errorDescription"`
		} `xml:"UPnPError"`
	} `xml:"Body>Fault>detail"`
}

// PortMapping GetGenericPortMappingEntryResponse action response
type PortMapping struct {
	// remote host ip address
	Host string
	// external port
	ExtPort int
	// portocol TCP or UDP
	Protocol string
	// ip address behind NAT device
	LocalIP string
	// listen port behind NAT device
	LocalPort int
	// enabled
	Enabled int
	// description
	Description string
	// duration time of seconds
	Duration int
}

// Do get port mapping list
func (a *GetPortMappingAction) Do(i int) (*ActionResponse, error) {
	obj, err := a.requestBody(i)
	if err != nil {
		return nil, err
	}
	bodyData, err := xml.Marshal(obj)
	if err != nil {
		return nil, err
	}
	data := xml.Header + string(bodyData)
	resp, err := a.sendRequest(strings.NewReader(data), len(data))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var ret getPortMappingResponse
	if err := xml.NewDecoder(resp.Body).Decode(&ret); err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		if ret.Detail.UPnPError.Code == 713 {
			return nil, errorNotFound{fmt.Sprintf("index %d", i)}
		}
		return nil, fmt.Errorf("GetGenericPortMappingEntryResponse failed: %d/%s",
			ret.Detail.UPnPError.Code, ret.Detail.UPnPError.Description)
	}
	return &ActionResponse{
		Code: 0,
		Data: PortMapping{
			Host:        ret.Body.Host,
			ExtPort:     ret.Body.ExtPort,
			Protocol:    ret.Body.Protocol,
			LocalIP:     ret.Body.LocalIP,
			LocalPort:   ret.Body.LocalPort,
			Enabled:     ret.Body.Enabled,
			Description: ret.Body.Description,
			Duration:    ret.Body.Duration,
		},
	}, nil
}
